/*
 * Package il.ac.biu.cs.grossmm.impl.simple
 * File PresenceEventPackage.java
 * 
 * This is a part of presence service framework prototype. 
 * See javadoc for more information.
 *  
 * Copyright (C) 2006 Grossmann Mark
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package il.ac.biu.cs.grossmm.impl.simple;

import static il.ac.biu.cs.grossmm.api.data.NodeOf.nodeOf;
import static il.ac.biu.cs.grossmm.api.keys.KeyFactory.entry;
import static il.ac.biu.cs.grossmm.api.keys.KeyFactory.key;
import static il.ac.biu.cs.grossmm.api.keys.PatternFactory.mandatory;
import static il.ac.biu.cs.grossmm.api.keys.PatternFactory.optional;
import static il.ac.biu.cs.grossmm.api.keys.PatternFactory.pattern;
import static il.ac.biu.cs.grossmm.api.presence.BaseVocabulary.CONTENT_TYPE;
import static il.ac.biu.cs.grossmm.api.presence.BaseVocabulary.FILTER;
import static il.ac.biu.cs.grossmm.api.presence.BaseVocabulary.RESOURCE_TYPE;
import il.ac.biu.cs.grossmm.api.OperationFailedException;
import il.ac.biu.cs.grossmm.api.data.NodeOf;
import il.ac.biu.cs.grossmm.api.data.Root;
import il.ac.biu.cs.grossmm.api.flow.NoSuchSubscription;
import il.ac.biu.cs.grossmm.api.flow.NodeEventInspector;
import il.ac.biu.cs.grossmm.api.flow.NodeInspector;
import il.ac.biu.cs.grossmm.api.flow.Status;
import il.ac.biu.cs.grossmm.api.flow.Subscriber;
import il.ac.biu.cs.grossmm.api.flow.SubscriptionPoint;
import il.ac.biu.cs.grossmm.api.keys.Key;
import il.ac.biu.cs.grossmm.api.keys.KeyPattern;
import il.ac.biu.cs.grossmm.api.presence.Entity;
import il.ac.biu.cs.grossmm.api.presence.Registrar;
import il.ac.biu.cs.grossmm.api.presence.ResourceType;
import il.ac.biu.cs.grossmm.api.server.ComponentManager;
import il.ac.biu.cs.grossmm.api.server.ServiceManager;
import il.ac.biu.cs.grossmm.api.simple.InternalSubscriptionBase;
import il.ac.biu.cs.grossmm.api.simple.Payload;
import il.ac.biu.cs.grossmm.api.simple.SipEventPackageBase;
import il.ac.biu.cs.grossmm.api.simple.SipSubscription;

import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;

import javax.activation.MimeType;
import javax.activation.MimeTypeParseException;
import javax.sip.address.SipURI;
import javax.sip.address.URI;
import javax.sip.header.ContentTypeHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;

import org.apache.log4j.Logger;

public class PresenceEventPackage extends SipEventPackageBase
	implements Subscriber<NodeOf<byte[]>>{
    /**
     * Logger for this class
     */
    private static final Logger logger = Logger
            .getLogger(PresenceEventPackage.class);
    
    Registrar registrar;

    final static String[][] mimeTypes = { { "application", "pidf+xml" } };
    
    final static KeyPattern pattern = 
    	pattern(Entity.class, 
    			mandatory(RESOURCE_TYPE, ResourceType.PRESENCE),
    			mandatory(CONTENT_TYPE, MimeType.class),
    			optional(FILTER, Object.class)
    			);
    
    SubscriptionPoint<NodeOf<byte[]>> sp;
    
    ConcurrentHashMap<Key, Internal> internals = new ConcurrentHashMap<Key, Internal>();

    public PresenceEventPackage() {
        super("SIMPLE Presence Event Notification Package Module");
    }
    
    @Override
    public void setComponentManager(ComponentManager container) throws Exception {
    	super.setComponentManager(container);
    	
    	registrar = container.getByClass(Registrar.class);
    }

    @Override
    public synchronized void start(ServiceManager serviceManager) throws Exception {
    	super.start(serviceManager);
    	sp = adm.createSubscriptionPoint(this, pattern, nodeOf(byte[].class));
    }

    public String getPackageName() {
        return "presence";
    }

    public String[][] getMimeTypes() {
        return mimeTypes;
    }

    public boolean isTemplatePackage() {
        return false;
    }

    public int getDefaultExpiration() {
        return 3600;
    }

    public int getMinimumExpiration() {
        return 0;
    }

	public Response subscribe(URI uri, Request subscribeReq, SipSubscription sipSub) {
        if (logger.isDebugEnabled()) {
            logger.debug("getResource() - start");
        }

        String scheme = uri.getScheme();

        // check scheme
        // TODO: allow more schemes

        if (!scheme.equals("sip") && !scheme.equals("sips")) {
            // Respond with a 416 (RFC3261, 8.2.2.1)
            if (logger.isDebugEnabled()) {
                logger.debug("getResource() -> scheme " + scheme
                        + " is not sip/sips");
            }
            Response resp = provider.createResponse(
            		Response.UNSUPPORTED_URI_SCHEME,
                    scheme + " is not supported");

            return resp;
        }

        SipURI sipUri = ((SipURI) uri);
        String user = sipUri.getUser();
        Entity presentity;

        // obtain presentity
        try {
			presentity = registrar.getEntity(user);
		} catch (OperationFailedException e) {
			return provider.createResponse(Response.SERVER_INTERNAL_ERROR, 
					"Could not get presentity: "+e.getMessage());
		}
        
        ContentTypeHeader cth = (ContentTypeHeader) subscribeReq.getHeader(ContentTypeHeader.NAME);
        String main = cth.getContentType();
        String sub = cth.getContentSubType();
        
        MimeType mimeType;
		try {
			mimeType = new MimeType(main, sub);
		} catch (MimeTypeParseException e) {
			return provider.createResponse(Response.SERVER_INTERNAL_ERROR, 
					"Could not parse mime type: "+e.getMessage());
		}
        
        Iterator it = cth.getParameterNames();
        
        while( it.hasNext() ) {
        	String name = (String) it.next();
        	mimeType.setParameter(name, cth.getParameter(name));
        }
        
        Key key =
        	key(presentity,
        			entry(RESOURCE_TYPE, ResourceType.PRESENCE),
        			entry(CONTENT_TYPE, mimeType));
        
        Internal newInternal = new Internal(key);
        Internal internal = internals.putIfAbsent(key, newInternal);

        internal.addSipSubscription(sipSub);
        
        if( newInternal != internal ) {
        	enProvider.initialized(sipSub);
        } else {
	        sp.subscribe(key, null);
	        
	        if (logger.isDebugEnabled()) {
	            logger.debug("getResource(URI, EventHeader) - end");
	        }
        }
        
        return null;
    }

    class Internal extends InternalSubscriptionBase {
    	Root<NodeOf<byte[]>> root;
    	Payload payload;

		public Internal(Key key) {
			super(key);
		}

		@Override
		protected void close() {
			sp.unsubscribe(key);
		}

		public Payload getPayload(SipSubscription sipSub) {
			return payload;
		}

		public Payload getPayload(NodeEventInspector inspector, SipSubscription sipSub) {
			return payload;
		}
    }

	public void activated(Key key, Root<NodeOf<byte[]>> root) {
		Internal internal = internals.get(key);
		internal.root = root;
		
		NodeInspector ins = sp.readLock(root);
		try {
			byte[] bytes = ins.getValue(root, NodeOf.property(byte[].class));
			MimeType mimeType = (MimeType) internal.getKey().value(CONTENT_TYPE);
			
			internal.payload = new Payload(bytes, mimeType);
		} catch (OperationFailedException e) {
			logger.error(e);
		}
		sp.readUnlock(root);
		
		enProvider.setState(internal, Status.ACTIVE);
	}

	public void deactivated(Key key, Status status) throws NoSuchSubscription {
		Internal internal = internals.get(key);
		internal.root = null;
		internal.payload = null;
		
		enProvider.setState(internal, status);
	}

	public void notify(Root<NodeOf<byte[]>> root, NodeEventInspector ins) {
		Key key = root.getKey(); 
		
		Internal internal = internals.get(key);
		
		try {
			byte[] bytes = ins.getValue(root, NodeOf.property(byte[].class));
			MimeType mimeType = (MimeType) internal.getKey().value(CONTENT_TYPE);
			
			internal.payload = new Payload(bytes, mimeType);
		} catch (OperationFailedException e) {
			logger.error(e);
		}
	}

	public ResourceType getResourceType(String eventType) {
		return ResourceType.PRESENCE;
	}
}
